/**
 *	The NeuroCoSA Toolkit
 *	Copyright (C) 2003-6 Stuart Meikle.
 *
 *	This is free software; you can redistribute it and/or
 *	modify it under the terms of the GNU Lesser General Public
 *	License as published by the Free Software Foundation; either
 *	version 2.1 of the License, or (at your option) any later version.
 *
 *	This library is distributed in the hope that it will be useful,
 *	but WITHOUT ANY WARRANTY; without even the implied warranty of
 *	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *	Lesser General Public License for more details.
 *
 * @author	Stuart Meikle
 * @version	2006-halloween(mk2)
 * @license	LGPL
 */
package org.stumeikle.NeuroCoSA;

/* Comments:
//
// 20031015 stumeikle	starting to port the alife/neuron stuff to java
// 20040228 stumeikle	changing to allow context control of synapses.
//			doesnt look like we need a change though
// 20040317 stumeikle	more changes. attempting to add drive/learning distinction
//			at neuron level. all neurons will need updating...
// 20041108 stumeikle	providing a means to access lateral connections (kind of means, hidden connections,
//			rather than lateral connections)
// 20041111 stumeilke	adding LOBE info, purely for debugging...
// 20041112 stumeikle	removing all relating to neuron mode. think this is an abstraction now. will move
//			drive_mode and learn_mode to the derived classes.
*/

import java.util.*;
import java.awt.*;
import java.awt.event.*;
import java.lang.*;
import java.util.*;
import javax.swing.*;
import javax.swing.border.*;
import javax.swing.table.*;
import javax.swing.event.*;

//FUTURE: i think iLastUpdateTime can be removed from here. 

/**
 * Neurons and synapses form the basis of learning in neurocosa.
 * Neuron class here provides the basic mechanisms for learning and the 
 * transferral of vesicles through the neuron.
 * This class should be used as a base class in specific neuron 
 * implementations.
 */
public class	Neuron
{
    public 	static double		default_excitation_value = 1.0; /**stumeikle 20041019 */
    public	static double		default_inhibition_value =-1.0; /**stumeikle 20041019 */

    public static final int		state_undefined=-1;
    public static final int		state_inactive=0;
    public static final int		state_excited=1;
    public static final int		state_inhibited=2;
    public static final int		state_dead=3;
					/** marked as this when the neuron is no longer 
   					* part of a lobe
					*/

    protected	Layer			iParent;
    protected	long			iId;	/**neuron ID. At least unique to lobe but not nec. to brain */
    protected	int			iState, /** current state the neuron is in */
    					iLastState; /** last state the neuron was in */
    protected	boolean			iSynapseTouched;/** working var */
    private	Vesicle			iVesicle; /** the output of this neuron */
    private	long			iLastUpdateTime; /** used to help solve synchronisation problems */
    protected	double			iExThres, /** threshold for excitation. could be static maybe */
    					iInThres; /** threshold for inhibition. could be static maybe */
    protected	double			iSigStr;/** output signal strength */
    protected	LinkedList<Synapse>	iInputs;/**list of synapses which form inputs to this neuron */
    protected	LinkedList<Synapse>	iOutputs;/** list of synapses which are the outputs from this neuron */
    protected	long			iCreationTime;/** when was this neuron created */

    //DEBUG info
    public	int			dbg_id;

//resist the addition of vesicle information here    
//definately no vesicle package

    /**
     * Constructor. 
     * @param list of lobes. This neuron is permitted to connect its output to 
     * neurons in these lobes
     */
    public Neuron( Layer l)
    { 
      iParent=l;
      doConstruct();
    }
   
    /** internal constructor method */
    private void doConstruct()
    {
        iId = 0;
	iState = iLastState = state_undefined; 
	iSynapseTouched=false;
	iVesicle = null;
	iLastUpdateTime = -1;
	iExThres = default_excitation_value * 0.1;/**stumeikle 20041019 */
	iInThres = default_inhibition_value * 0.1;/**stumeikle 20041019 */
	iSigStr=0;

	dbg_id = -1;
	iInputs = new LinkedList<Synapse>();
	iOutputs= new LinkedList<Synapse>();
	iCreationTime = iParent.getBrainTime();
    }

    //V PUBLIC inspectors and modifiers --------------------------------------------------
    public long	getID()
    {
    	return iId;
    }

    public void	setID(long l)
    {
    	iId=l;
    }

    public long			getCreationTime()
    { return iCreationTime; }

    public LinkedList<Synapse>	getInputs()
    { return iInputs; }

    public LinkedList<Synapse>	getOutputs()
    { return iOutputs; }


    public Layer		getLayer()
    { return iParent; }

    public int			getState()
    {
    	return iState;
    }

    public double		getExcitationThreshold()
    { return iExThres; }

    public void			setState( int i )
    { iState = i ; }

    public double		getSignalStrength()
    {
     	return iSigStr;
    }

    /** add the given synapse as an input to this neuron
    * @param input the given synapse
    */
    public void    		connectInput( Synapse input )
    {
    	iInputs.add(input);
	
	//ADD ? iParent.notifyChange( BrainChangeMonitor.neuron_input_added );
    }

    /** disconnect the given input synapse from this neuron
    * @param input the given synapse
    */
    public void			disconnectInput( Synapse s )
    {
	//find the synapse in our list and remove the
	//list entry. easy in java
	iInputs.remove(s);
	//ADD ? iParent.notifyChange( BrainChangeMonitor.neuron_input_removed );
    }
    
    public void			connectOutput( Synapse s )
    {
        iOutputs.add(s);
	//ADD ? iParent.notifyChange( BrainChangeMonitor.neuron_output_added );
    }
    

    public final void		update()
    {
	update(null);
    }

    /** the heart of the machine
     * update the neuron. Steps involved: create a single pacakge from all incoming
     * vesicles. Consider changing state to excited or inhibited. Notify the inputs of the 
     * neuron state. If excited , propagate the vesicle package to the outputs. 
     * Finally , update synaptic connections 
     * 20051005 stumeikle	Trial code, split into two parts. Can this help with auto
     *				lateral inhibition ( and also perhaps probe neurons ? )
     */
    public final void	update(Vesicle v) //stumeikle 20040606 pass in layer control
    {
    	if (iState!=state_dead)
	{
    	    update_step1(v); //need to pass this in to enable lateral inhibition
	    update_step2();
	}
    }

    public void update_step1( Vesicle lv ) /** create the vesicle pacakge */
    {
        //The heart of the machine
        //(0) reset the temp var for the learning process
    	iSynapseTouched=false;
	
	//(0.01) set the update time
	iLastUpdateTime = iParent.getBrainTime();
	
	//(0.5) 20050824
// 	if (iState == NeuronState.dead)	
// 	{	
// 		iVessyP = null;
// 		return;
// 	}
				
	//(1)create a single vesicle from all the incoming vesicles.
	iVesicle = createVesicle(lv);
	if (iVesicle==null) 	return;
    	
	//(1.5) with lateral drive inhibition we'll have to break here and collect the VPs from all
	//neurons. Then select the strongest drive signal and input that into all neurons again to
	//compute the final vp content. 
	return;
    }

    public void update_step2() /** process the vesicle package and propagate*/
    {
    	//(2)deciIde if we become excited or inhibited or inactive
	if (iState==state_dead)	return;
	if (iVesicle==null)
	{
	    //update stumeikle 20061203 CHECK
	    iState = state_inactive;
	    return;
	}
    	
	double s = iVesicle.getSignalStrength();
	iLastState=iState;
    	if (s>iExThres)
    	    iState = state_excited;
    	else if (s<iInThres)
    	    iState = state_inhibited;
    	else
    	    iState = state_inactive;

    	iSigStr=iVesicle.getSignalStrength();

	//(3)if the iState has changed, call functions to process this.
	//(maybe in the FUTURE)...
	
	//(3.05) review the behaviour we had from the previous round
	reviewBehaviour();
	
	//(3.1)call any special functions
	if (iState ==state_excited)
    	    performExcitedTasks();
	if (iState ==state_inactive)
	    performInactiveTasks();

	//(3.2) 20040301 . these last are overloadable so do our own special
	//ops now.
	//storeStateAtTime();
	//2006-halloween try to remove this. don't think its really needed 

	//(3.5)if we are switching off then reset the synapses
	if (iState ==state_inactive && iLastState !=state_inactive)
    	    resetSynapses();
	//(3.6) do the same if we switch to inhibited
	if (iState ==state_inhibited && iLastState !=state_inhibited)
    	    resetSynapses();

	//(3.6)update the iLastState var.
        iLastState = iState;

    	//(4)notify the iInputs of our activity
    	notifyiInputsOfActivity();

    	//(5)if excited, propagate the vesicle
    	if (iState==state_excited)
    	{
	    updateSynapses(iVesicle);
	    
	    //(6) update the synaptic connections. 
            updateConnections( iVesicle );
        }
        else if (iState==state_inactive)
	    ;
        else if (iState==state_inhibited)
	    ;

        // That's All Folks ...
    }


    public void			setSynapseTouched()
    {
    	iSynapseTouched=true;
    }


    /** optional method. expected to be overloaded */
    public int			getNumLateralConnections()
    {
    	return 0; 
    }

    /**
     * Notify the inputs of the neurons current state 
     */
    protected	void		notifyiInputsOfActivity()
    {
	//tell the iInputs what our iState is
	ListIterator	it = iInputs.listIterator(0);

	for( ; it.hasNext();)
    	{
	    Synapse	s = (Synapse) it.next();

	    s.setOutputNeuronState( iState );
        }
    }

    /** Notify the outputs that the neuron is switching to inactive state */
    protected	void		resetSynapses()
    {
    	ListIterator	it = iOutputs.listIterator(0);
    	for(; it.hasNext();)
        {
    	    Synapse	s = (Synapse) it.next();
    	    s.reset();
    	}
    }

    /** Update the synapses. This is provided as a separate routine so that 
     *  Neurons can use it to update their synapses additional times, in special circumstances
     */
    public	void		updateSynapses(Vesicle v)
    {
        ListIterator i = iOutputs.listIterator(0);
	    
	for(;i.hasNext();)
	{
	    Synapse	sin = (Synapse) i.next();

	    //why do we have these two? couldn't we make a single
	    //call? stumeikle 20031111
	    //sin.respondToSignal(vp); removed. all synapses must
	    //claim vesicles during update.
	    sin.update(v);

	    if (sin.requestsDisconnect())
	    {
		//remove s from the list
		i.remove();
		//make sure that the synapse disconnects from the input of the other body
		sin.destructor();
	//ADD ?	iParent.notifyChange( BrainChangeMonitor.neuron_output_removed );
	    }
	}
    }

    //------------------------------- API for derived forms -----------------------------------
    /** Create a vesicle package from the inputs. This method must be overloaded
     */
    protected	Vesicle 	 createVesicle(Vesicle vt) //virtual
    {
        System.out.println("Neuron::createVesicle() called, please overload");
	return null;
    }

    /** Review actions. This method can be overloaded 
     *  If the neuron has been "driven" then its outputs should have caused a particular change in
     *  its inputs. If this has not been the case, connections should be weakened. 
     *  If it was the case connections should be strengthened. 
     *  Tied in with ideas of Expectation and Surprise
     */
    protected	void		reviewBehaviour() //virtual
    {
    }

    /** virtual function. overridden by derived classes */
    public	void		performExcitedTasks() {} 
    /** virtual function. overridden by derived classes */
    public	void		performInactiveTasks(){} 
    /** virtual function. overridden by derived classes */
    public	void		updateConnections(Vesicle v)
    				{} //virtual



    public	void		destroyAllSynapses()
    {
	ListIterator i = iOutputs.listIterator(0);
	    
	for(;i.hasNext();)
	{
	    Synapse	sin = (Synapse) i.next();
	    i.remove();
	    //make sure that the synapse disconnects from the input of the other body
	    sin.destructor();
//ADD?	    iParent.notifyChange( BrainChangeMonitor.neuron_output_removed );
	}
    }

    //debug routines
    public	int	getDEBUGID()
    { return dbg_id; }

    public	void	setDEBUGID(int i)
    { dbg_id = i; }

    public	String	getDEBUGLabel()	
    {
	//should be overloaded by the user
	return "no-name";
    }
}
